﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Web;
using System.Data;

namespace Rookey.Frame.IMCore
{

	public class ResponsesListener : IAsyncResult
	{
		AsyncCallback m_AsyncCallback = null;
		object m_Data = null;
		bool m_IsCompleted = false;

		String _sessionId;
		DateTime _createTime;

		public DateTime CreateTime
		{
			get { return _createTime; }
		}

		public String SessionID
		{
			get { return _sessionId; }
		}

		public ResponsesListener(String sessionId, AsyncCallback callback, Object extraData)
		{
			m_Data = extraData;
			m_AsyncCallback = callback;
			_createTime = DateTime.Now;
			_sessionId = sessionId;
		}

		bool IAsyncResult.IsCompleted { get { return m_IsCompleted; } }

		bool IAsyncResult.CompletedSynchronously { get { return false; } }

		WaitHandle IAsyncResult.AsyncWaitHandle { get { return null; } }

		Object IAsyncResult.AsyncState { get { return m_Data; } }

		String m_Cache = "";

		public void Cache(object content)
		{
			m_Cache = content.ToString();
		}

		public void Send(HttpContext context)
		{
#			if TRACE
			ServerImpl.Instance.WriteLog(String.Format("Send Responses:SessionID = \"{0}\", Content=\'{1}\'", _sessionId, m_Cache.ToString()));
#			endif
			context.Response.Write(m_Cache.ToString());
		}

		public void Complete(object data)
		{
			m_AsyncCallback(this);
			m_IsCompleted = true;
		}
	}

	public class CommandResponse : IRenderJson
	{
		public String CommandID;
		public String Data;

		public CommandResponse(String cmd, String data)
		{
			CommandID = cmd;
			Data = data;
		}

		void IRenderJson.RenderJson(StringBuilder builder)
		{
			Utility.RenderHashJson(builder, "CommandID", CommandID, "Data", new JsonText(Data));
		}
	}

	public class AccountSession
	{
		object _lock = new object();

		DateTime _createdTime = DateTime.Now;

		public DateTime LatestAccessTime
		{
			get { return _createdTime; }
		}

		ResponsesListener _listener = null;

		List<CommandResponse> _cache = new List<CommandResponse>();

		string _sessionId = String.Empty;

		public string SessionID
		{
			get { return _sessionId; }
		}

		LinkedListNode<ClearSessionNode> _node = null;

		public LinkedListNode<ClearSessionNode> ListNode
		{
			get { return _node; }
		}

		public AccountSession(string username, string id)
		{
			_sessionId = id;
			_node = new LinkedListNode<ClearSessionNode>(new ClearSessionNode(username, id, this));
		}

		public bool Receive(ResponsesListener listener)
		{
			lock (_lock)
			{
				_createdTime = DateTime.Now;

				if (_cache.Count > 0)
				{
					String json = Utility.RenderHashJson("IsSucceed", true, "Responses", _cache);
					listener.Cache(json);
					_cache.Clear();
					return true;
				}
				else
				{
					_listener = listener;
					return false;
				}
			}
		}

		public void Send(String commandId, String data)
		{
			lock (_lock)
			{
				_cache.Add(new CommandResponse(commandId, data));
				SendCache();
			}
		}

		public void SendCache()
		{
			lock (_lock)
			{
				if (_listener != null)
				{
					try
					{
						String json = Utility.RenderHashJson("IsSucceed", true, "Responses", _cache);
						_listener.Cache(json);
						_cache.Clear();
					}
					finally
					{
						ThreadPool.QueueUserWorkItem(_listener.Complete);
						_listener = null;
					}
				}
			}
		}
	}

	public class AccountState
	{
		string m_User;

		public string UserName
		{
			get { return m_User; }
		}
		Hashtable m_Sessions = new Hashtable();
		DateTime m_LastAccessTime = DateTime.Now;
		Hashtable m_Config = new Hashtable();

		private void LoadConfig(string type, string def)
		{
			try
			{
				using (System.IO.Stream stream = Rookey.Frame.IMCore.IO.File.Open(String.Format("/{0}/Config/{1}", m_User, type), System.IO.FileMode.OpenOrCreate, System.IO.FileAccess.Read, System.IO.FileShare.Read))
				{
					Byte[] buffer = new Byte[stream.Length];
					stream.Read(buffer, 0, buffer.Length);
					string content = Encoding.UTF8.GetString(buffer);
					if (String.IsNullOrEmpty(content)) content = def;

					Hashtable config = Utility.ParseJson(content) as Hashtable;
					m_Config[type.ToUpper()] = config;
				}
			}
			catch
			{
				Hashtable config = Utility.ParseJson(def) as Hashtable;
				m_Config[type.ToUpper()] = config;
			}
		}

		private void SaveConfig(string type)
		{
			try
			{
				using (System.IO.Stream stream = Rookey.Frame.IMCore.IO.File.Open(String.Format("/{0}/Config/{1}", m_User, type), System.IO.FileMode.Create, System.IO.FileAccess.Write, System.IO.FileShare.None))
				{
					Hashtable config = m_Config[type.ToUpper()] as Hashtable;
					Byte[] buffer = Encoding.UTF8.GetBytes(Utility.RenderJson(config));
					stream.Write(buffer, 0, buffer.Length);
				}
			}
			catch
			{
			}
		}

		static string DefaultMsgConfig = Utility.RenderHashJson(
			"LastReceivedTime", new DateTime(2000, 1, 1)
		);

		private void LoadConfig()
		{
			LoadConfig("message.conf", DefaultMsgConfig);
		}

		private void SaveConfig()
		{
			Hashtable config = GetConfig("message.conf");
			lock (config)
			{
				DateTime lrt = (DateTime)config["LastReceivedTime"];
				if (lrt > m_LastAccessTime) config["LastReceivedTime"] = m_LastAccessTime;
			}
			SaveConfig("message.conf");
		}

		public AccountState(string user)
		{
			m_User = user;
			LoadConfig();
		}

		public DateTime LastReceivedTime
		{
			get
			{
				DateTime from = (DateTime)(m_Config["MESSAGE.CONF"] as Hashtable)["LastReceivedTime"];
				if (from > m_LastAccessTime) from = m_LastAccessTime;
				return from;
			}
		}

		public Hashtable GetConfig(string type)
		{
			return m_Config[type.ToUpper()] as Hashtable;
		}

		private void SendUnreadMessage(AccountSession session)
		{
			DateTime from;
			lock (this)
			{
				from = (DateTime)GetConfig("MESSAGE.CONF")["LastReceivedTime"];
				if (from > m_LastAccessTime) from = m_LastAccessTime;
			}

			List<Message> msgs = MessageImpl.Instance.Find(m_User, "*", from);
			string data = String.Empty;

			if (msgs.Count > 0)
			{
				data = Utility.RenderHashJson(
					"Peer", "*",
					"Messages", msgs
				);

				Hashtable config = GetConfig("MESSAGE.CONF");
				lock (config)
				{
					foreach (Message message in msgs)
					{
						DateTime lrt = (DateTime)config["LastReceivedTime"];
						if (lrt < message.CreatedTime) config["LastReceivedTime"] = message.CreatedTime;
					}
				}

				lock (this)
				{
					SaveConfig();
				}
			}
			else
			{
				data = Utility.RenderHashJson(
					"Peer", "*",
					"Messages", JsonText.EmptyArray
				);
			}

			session.Send("GLOBAL:IM_MESSAGE_NOTIFY", data);
		}

		public void NewSession(string sessionId)
		{
			AccountSession session = null;
			lock (this)
			{
				if (!m_Sessions.ContainsKey(sessionId))
				{
					m_Sessions[sessionId] = (session = new AccountSession(m_User, sessionId));
				}
				session = m_Sessions[sessionId] as AccountSession;
#				if TRACE
				ServerImpl.Instance.WriteLog(String.Format("New Session:SessionID = \"{0}\", UserName=\'{1}\'", sessionId, m_User));
#				endif
			}
			if (session != null)
			{
				SendUnreadMessage(session);
				SessionManagement.Instance.Insert(session);
            }

            AccountInfo info = AccountImpl.Instance.GetUserInfo(UserName);
            SessionManagement.Instance.Send("UserStateChanged", Utility.RenderHashJson("User", info.ID, "State", "Online", "Details", info.DetailsJson));
		}

		public bool Receive(string sessionId, ResponsesListener listener)
		{
			AccountSession session = null;
			bool reset = false;

			lock (this)
			{
				if (!m_Sessions.ContainsKey(sessionId))
				{
					m_Sessions[sessionId] = new AccountSession(m_User, sessionId);
					reset = true;
				}

				m_LastAccessTime = DateTime.Now;

				session = m_Sessions[sessionId] as AccountSession;
			}

			if (reset)
			{
#				if TRACE
				ServerImpl.Instance.WriteLog(String.Format("Reset Session:SessionID = \"{0}\", UserName=\'{1}\'", sessionId, m_User));
#				endif
				session.Send("GLOBAL:SessionReset", "null");
			}

			if (session != null)
			{
				SessionManagement.Instance.Insert(session);
			}

			return session.Receive(listener);
		}

		public void Timeout(String sessionId)
		{
			lock (this)
			{
				AccountSession session = m_Sessions[sessionId] as AccountSession;
				session.SendCache();
			}
		}

		public void Remove(String sessionId)
		{
			lock (this)
			{
				AccountSession session = m_Sessions[sessionId] as AccountSession;
				session.SendCache();
				m_Sessions.Remove(sessionId);
                if (m_Sessions.Count == 0)
                {
                    SaveConfig();
                }
			}
		}

		public void Send(String commandId, String data)
		{
			List<String> ol = new List<string>();

			lock (this)
			{
				foreach (DictionaryEntry ent in m_Sessions)
				{
					(ent.Value as AccountSession).Send(commandId, data);
				}
			}
		}

		public Boolean IsOnline
		{
			get
			{
				return m_Sessions.Count > 0;
			}
		}

		public DateTime LastAccessTime
		{
			get { return m_LastAccessTime; }
		}
	}

	public class ClearSessionNode
	{
		public DateTime InsertTime;

		String _userName, _sessionID;

		AccountSession _session;

		public AccountSession Session
		{
			get { return _session; }
		}

		public String SessionID
		{
			get { return _sessionID; }
		}

		public String UserName
		{
			get { return _userName; }
		}

		public ClearSessionNode(string username, string sessionid, AccountSession session)
		{
			_userName = username;
			_sessionID = sessionid;
			_session = session;
		}
	}

	public class SessionManagement
	{
		static SessionManagement m_Instance = new SessionManagement();

		static public SessionManagement Instance
		{
			get { return m_Instance; }
		}

		public const Int32 TIMER_PERIOD = 20 * 1000;
		public const Int32 SESSION_ONLINE_TIMEOUT = 120 * 1000;
		public const Int32 SESSION_TIMEOUT = 55 * 1000;

		LinkedList<ClearSessionNode> m_ClearSessionList = new LinkedList<ClearSessionNode>();

		Timer m_Timer = null;

		private void TimerProc(object state)
		{
			try
			{
				List<LinkedListNode<ClearSessionNode>> removeNodes = new List<LinkedListNode<ClearSessionNode>>();
				List<ClearSessionNode> timeoutNodes = new List<ClearSessionNode>();

#				if TRACE
				StringBuilder sessions = new StringBuilder();
#				endif

				lock (m_ClearSessionList)
				{
					DateTime now = DateTime.Now;

					LinkedListNode<ClearSessionNode> n = m_ClearSessionList.First;
					while (n != null)
					{
						double diff = (now - n.Value.InsertTime).TotalMilliseconds;
						if (diff > SESSION_ONLINE_TIMEOUT) removeNodes.Add(n);
						else if (diff > SESSION_TIMEOUT) timeoutNodes.Add(n.Value);
						else break;
						n = n.Next;
					}

					foreach (LinkedListNode<ClearSessionNode> rn in removeNodes)
					{
						m_ClearSessionList.Remove(rn);

#						if TRACE
						if (sessions.Length > 0) sessions.Append(",");
						sessions.AppendFormat("({0},{1})", rn.Value.UserName, rn.Value.SessionID);
#						endif
					}

#					if TRACE
					ServerImpl.Instance.WriteLog(String.Format("Clear Sessions Timer:Session Count = {0}", m_ClearSessionList.Count));
#					endif

				}

#				if TRACE
				lock (m_Accounts)
				{
					ServerImpl.Instance.WriteLog(String.Format("Clear Sessions Timer:Account Count = {0}", m_Accounts.Count));
				}
				ServerImpl.Instance.WriteLog(String.Format("Clear Sessions Timer:Clear = \"{0}\"", sessions));
#				endif

				foreach (ClearSessionNode tn in timeoutNodes)
				{
					try
					{
						AccountState s = GetAccountState(tn.UserName);
						if (s != null) s.Timeout(tn.SessionID);
					}
					catch
					{
					}
                }

                Hashtable offlineNotifyUsers = new Hashtable();

				foreach (LinkedListNode<ClearSessionNode> rn in removeNodes)
				{
					try
					{
                        AccountState s = GetAccountState(rn.Value.UserName);
                        AccountInfo info = AccountImpl.Instance.GetUserInfo(rn.Value.UserName);
                        offlineNotifyUsers[info.ID] = rn.Value.UserName;
						if (s != null) s.Remove(rn.Value.SessionID);
						if (!s.IsOnline)
						{
							lock (m_Accounts)
							{
								m_Accounts.Remove(s.UserName.ToUpper());
							}
						}
					}
					catch
					{
					}
                }

                foreach (DictionaryEntry ent in offlineNotifyUsers)
                {
                    if (!SessionManagement.Instance.IsOnline(ent.Value as string))
                    {
                        AccountInfo info = AccountImpl.Instance.GetUserInfo(Convert.ToInt64(ent.Key));
                        Send("UserStateChanged", Utility.RenderHashJson("User", ent.Key, "State", "Offline", "Details", info.DetailsJson));
                    }
                }
			}
			catch
			{
			}
		}

		public void Insert(AccountSession session)
		{
			lock (m_ClearSessionList)
			{
				if (m_ClearSessionList != session.ListNode.List)
				{
					m_ClearSessionList.AddLast(session.ListNode);
				}
				else
				{
					m_ClearSessionList.Remove(session.ListNode);
					m_ClearSessionList.AddLast(session.ListNode);
				}
				session.ListNode.Value.InsertTime = DateTime.Now;
			}
		}

		private SessionManagement()
		{
			m_Timer = new Timer(this.TimerProc);
			m_Timer.Change(0, TIMER_PERIOD);
		}

		Hashtable m_Accounts = new Hashtable();

		public AccountState GetAccountState(string user)
		{
			lock (m_Accounts)
			{
				string key = user.ToUpper();
				if (!m_Accounts.ContainsKey(key))
				{
					m_Accounts[key] = new AccountState(user);
				}
				return m_Accounts[key] as AccountState;
			}
		}

		public bool IsOnline(string user)
		{
			lock (m_Accounts)
			{
				string key = user.ToUpper();
				if (!m_Accounts.ContainsKey(key)) return false;
				else return (m_Accounts[key] as AccountState).IsOnline;
			}
		}

		public void Send(string user, string command, string data)
		{
			AccountState state = GetAccountState(user);
			if (state != null) state.Send(command, data);
        }

        public void Send(string command, string data)
        {
            List<AccountState> accs = new List<AccountState>();
            lock (m_Accounts)
            {
                foreach (DictionaryEntry ent in m_Accounts)
                {
                    accs.Add(ent.Value as AccountState);
                }
            }

            foreach (AccountState state in accs)
            {
                state.Send(command, data);
            }
        }
	}
}
